# -*- coding: utf-8 -*-

import base64
import json
import logging
import traceback

from odoo.http import request
from odoo_contrib.exceptions import BaseOdooException

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.padding import PKCS7
from weixin import WXAPPAPI

from ..utils.common import sentinel
from ..utils.auth import current_user
from ..utils.serializer import DefaultSerializer
from ..utils.redis import RedisClient
from ..utils.http import HttpClient
from ..utils.exceptions import WechatMiniAppException


logger = logging.getLogger(__name__)


class WechatMiniApp(object):
    app_type = 'miniapp'
    site = sentinel
    access_token_url = 'https://api.weixin.qq.com/cgi-bin/token'
    qrcode_url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit'
    sending_template_message_url = 'https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send'

    def __init__(self, site, app_entry):
        # todo: add error handler to missing of secret_key or session_expiration
        self.secret_key = request.env['ir.config_parameter'].sudo().get_param('wechat.secret_key') or None
        self.session_expiration = int(
            request.env['ir.config_parameter'].sudo().get_param('wechat.session_expiration')) or None
        if not app_entry.settings:
            raise WechatMiniAppException(
                errcode=201002,
                params={
                    'site': site,
                },
            )
        if not app_entry.settings.appid:
            raise WechatMiniAppException(
                errcode=201003,
                params={
                    'site': site,
                },
            )
        if not app_entry.settings.app_secret:
            raise WechatMiniAppException(
                errcode=201004,
                params={
                    'site': site,
                },
            )
        self._appid = app_entry.settings.appid
        self._app_secret = app_entry.settings.app_secret

        self._serializer = DefaultSerializer(
            key=self.secret_key,
            expiration=self.session_expiration,
        )
        self._wxapi = WXAPPAPI(
            appid=self._appid,
            app_secret=self._app_secret,
        )

        self._redis = RedisClient()
        HttpClient(self)

        self.access_token_default_expiration = \
            int(request.env['ir.config_parameter'].sudo().get_param('wechat.access_token_default_expiration'))
        self.access_token_expiration_tolerance = \
            int(request.env['ir.config_parameter'].sudo().get_param('wechat.access_token_expiration_tolerance'))

        self.qrcode_allowed_char = "!#$&'()*+,/:;=?@-._~" \
                                   + ''.join(chr(ord('a') + i) for i in range(26)) \
                                   + ''.join(chr(ord('A') + i) for i in range(26)) \
                                   + ''.join(str(i) for i in range(10))

    @property
    def wxapi(self):
        return self._wxapi

    @property
    def access_token(self):
        try:
            token = self._redis.load_access_token()
            if token:
                return token
            logger.info('Token may expired, retrieving it from tencent')
            token, expiration = self._get_access_token()
            try:
                self._redis.persist_access_token(token, expiration)
            except:
                pass
        except WechatMiniAppException:
            raise
        except:
            logger.error(
                'Error while retrieving access token: %s',
                traceback.format_exc(),
            )
            raise WechatMiniAppException(
                errcode=304050,
            )
        else:
            return token

    def _get_access_token(self):
        params = {
            'grant_type': 'client_credential',
            'appid': self._appid,
            'secret': self._app_secret,
        }
        ret = self.http_client.get(self.access_token_url, params=params)
        errcode = ret.get('errcode')
        if errcode:
            logger.error(
                'Tencent return error while retrieving access token. code: %s msg: %s',
                errcode,
                ret.get('errmsg'),
            )
            raise WechatMiniAppException(
                errcode=304001,
                return_data={
                    'errcode': errcode,
                    'errmsg': ret.get('errmsg'),
                }
            )
        access_token = ret.get('access_token')
        if not access_token:
            raise WechatMiniAppException(
                errcode=304002,
            )
        expiration = ret.get('expires_in', self.access_token_default_expiration)
        try:
            expiration = int(expiration)
        except:
            expiration = self.access_token_default_expiration
        expiration -= self.access_token_expiration_tolerance

        return access_token, expiration

    def get_wechat_qrcode(self, scene_data, page=None, data=None):
        try:
            scene_data = ['='.join((k, str(v))) for k, v in scene_data.items()]
            scene = ','.join(scene_data)
            for i in scene:
                if i not in self.qrcode_allowed_char:
                    logger.error(
                        'Illegal char. EXIT. %s',
                        scene,
                    )
                    raise WechatMiniAppException(
                        errcode=305001,
                        params={
                            'scene': scene,
                        }
                    )
            if self.access_token is None:
                logger.error(
                    'Retrieving access token failed. EXIT.'
                )
                raise WechatMiniAppException(
                    errcode=305002,
                )
            params = {
                'access_token': self.access_token,
            }
            data = data or {}
            data.update(scene=scene)
            if page:
                data.update(page=page)

            ret = self.http_client.post(
                self.qrcode_url,
                json_payload=data,
                params=params,
                raw=True
            )
            try:
                error = ret.json()
            except:
                pass
            else:
                logger.error(
                    'Wechat returned error while retrieving share-qrcode. params: %s err_info: %s',
                    params,
                    error,
                )
                raise WechatMiniAppException(
                    errcode=305003,
                    message=error.get('errmsg'),
                )
        except BaseOdooException:
            raise
        except:
            logger.error(
                'Error while retrieving qrcode from wechat. %s %s %s Err: %s',
                scene_data,
                page,
                data,
                traceback.format_exc(),
            )
            raise WechatMiniAppException(
                errcode=305050,
            )
        else:
            return ret.content

    # {'openId': 'oMsoG0SE7WqooVSAiOODolH19s70', 'nickName': 'SimonQ', 'gender': 1, 'language': 'zh_CN', 'city': 'Guangzhou', 'province': 'Guangdong', 'country': 'China', 'avatarUrl': 'https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKhDSfjEXX0xdoI6zbxNUkvnGViajRLbKh3UicSbvdDnVySNzhibDOZ2ZgOA3ic8zZdoFEID31pxSl0QA/132', 'watermark': {'timestamp': 1543286576, 'appid': 'wx171c2e7fe69df2c0'}}
    #  
    def decrypt_user_info(self, session_key, iv, encrypted):
        try:
            key = base64.b64decode(session_key)
            iv = base64.b64decode(iv)
            cipher = Cipher(
                algorithms.AES(key),
                modes.CBC(iv),
                backend=default_backend(),
            )
            decryptor = cipher.decryptor()
            plain = decryptor.update(base64.b64decode(encrypted)) + decryptor.finalize()
            unpadder = PKCS7(128).unpadder()
            decrypted = unpadder.update(plain)
            decrypted += unpadder.finalize()
            decrypted = json.loads(decrypted.decode('utf8'))
        except WechatMiniAppException:
            raise
        except:
            logger.error(
                'Error while decrypting user info: %s Err: %s',
                encrypted,
                traceback.format_exc(),
            )
            raise WechatMiniAppException(
                errcode=306050,
            )
        return decrypted

    def cache_form_ids(self, openid, form_ids):
        logger.debug('caching form ids for %s -> %s', openid, form_ids)

        self._redis.cache_form_ids(openid, form_ids)

    def get_formid(self, openid=None):
        if openid is None:
            openid = current_user.openid
        return self._redis.get_formid(openid)

    def send_template_message(self, template_id, to, data, page=None, emphasis_keyword=None):
        try:
            form_id = self.get_formid(to)
            if form_id is None:
                logger.error(
                    'No form_id found for user: %s template: %s data: %s page: %s. EXIT',
                    to,
                    template_id,
                    data,
                    page
                )
                return False

            if self.access_token is None:
                logger.error(
                    'Retrieving access token failed. EXIT.'
                )
                return False
            params = {
                'access_token': self.access_token,
            }

            payload = {
                'touser': to,
                'template_id': template_id,
                'form_id': form_id,
                'data': data,
            }
            if page is not None:
                payload.update(page=page)
            if emphasis_keyword is not None:
                payload.update(emphasis_keyword=emphasis_keyword)

            ret = self.http_client.post(
                self.sending_template_message_url,
                params=params,
                json_payload=payload,
                raw=True,
            )
            ret.raise_for_status()
            ret = ret.json()
            if ret.get('errcode'):
                logger.error(
                    'Tencent returns error while sending push-template message. '
                    'to: %s template_id: %s page: %s form_id: %s data: %s Err: %s -> %s',
                    to,
                    template_id,
                    page,
                    form_id,
                    data,
                    ret['errcode'],
                    ret.get('errmsg'),
                )
                return False
        except:
            logger.error(
                'Error while sending push-template message. to: %s template_id: %s page: %S formid: %s data: %s Err: %s',
                to,
                template_id,
                page,
                form_id,
                data,
                traceback.format_exc(),
            )
            return None
        else:
            return True
